#include "packetfactory.h"

QByteArray PacketFactory::Login(uint uin, QString password, NetworkType networkType)
{
    const uint & tmpSeq = nextSequenceId();

    DataWriter body(nullptr);
    body.writeShort(9);// subCommand
    body.writeShort(17);// count of TLVs, probably ignored by server?
    Tlv0x18(&body).pack(uin).release();
    Tlv0x1(&body).pack(uin).release();
    Tlv0x106(&body).pack(uin,password.toLatin1(),LoginType::PASSWORD).release();//it's wrong
    Tlv0x116(&body).pack().release();
    Tlv0x100(&body).pack().release();
    Tlv0x107(&body).pack().release();
    Tlv0x142(&body).pack().release();
    Tlv0x144(&body).pack(networkType).release();
    Tlv0x145(&body).pack().release();
    Tlv0x147(&body).pack().release();
    if((QQDeviceConfig::getInstance().miscBitMap & 0x80) !=0){
        Tlv0x166(&body).pack().release();
    }
    Tlv0x154(&body).pack(tmpSeq).release();
    Tlv0x141(&body).pack(networkType).release();
    Tlv0x8(&body).pack().release();
    Tlv0x511(&body).pack().release();
    Tlv0x187(&body).pack().release();
    Tlv0x188(&body).pack().release();
    Tlv0x194(&body).pack().release();
    Tlv0x191(&body).pack().release();
    Tlv0x202(&body).pack().release();
    Tlv0x177(&body).pack().release();
    Tlv0x516(&body).pack().release();
    Tlv0x521(&body).pack().release();
    Tlv0x525(&body).pack().release();

    DataWriter oicqPacket(nullptr);
    writeOicqRequestPacket(&oicqPacket,uin,0x0810,*body.getData());
    DataWriter ssoPacket(nullptr);
    writeSsoPacket(&ssoPacket,"wtlogin.login",tmpSeq,*oicqPacket.getData());
    DataWriter ret(nullptr);
    buildOutgoingPacket(&ret,uin,2,*ssoPacket.getData());
    return *ret.getData();
}

QByteArray PacketFactory::StatSvcRegister(const LoginSigInfo &loginSig, OnlineStatus onlineStatus)
{
    SvcReqRegister svcReqRegister;
    svcReqRegister.cConnType = 0;
    svcReqRegister.lBid = 1 | 2 | 4;
    svcReqRegister.lUin = loginSig.uin;
    svcReqRegister.iStatus = onlineStatus;
    svcReqRegister.bKikPC = 0;
    svcReqRegister.bKikWeak = 0;
    svcReqRegister.timeStamp = 0;
    svcReqRegister.iLargeSeq = 1551;
    svcReqRegister.bOpenPush = 1;
    svcReqRegister.iLocaleID = 2052;
    svcReqRegister.bRegType = 0;//TODO MORE TYPE FILL IT
    svcReqRegister.bIsSetStatus = 0;//TODO
    svcReqRegister.iOSVersion = QQDeviceConfig::getInstance().sdk;
    svcReqRegister.cNetType = 1;//TODO WIFI
    svcReqRegister.vecGuid = QQDeviceConfig::getInstance().guid;
    svcReqRegister.strDevName = QQDeviceConfig::getInstance().model;
    svcReqRegister.strDevType = QQDeviceConfig::getInstance().model;
    svcReqRegister.strOSVer = QQDeviceConfig::getInstance().release;
    svcReqRegister.uOldSSOIp = 0;
    svcReqRegister.uNewSSOIp = Util::getData<qint64>(QQDeviceConfig::getInstance().ssoIP);//00 01 00 01 00 00 00 7F
    svcReqRegister.strVendorName = QQDeviceConfig::getInstance().VendorName;
    svcReqRegister.strVendorOSName = QQDeviceConfig::getInstance().VendorOSName;
    qqprotobuf::Oidb::Oidb0x769 oidb0x769;
    auto config1 = oidb0x769.add_rpt_config_list();
    auto config2 = oidb0x769.add_rpt_config_list();
    config1->set_type(46);config1->set_version(0);
    config2->set_type(283);config2->set_version(0);
    svcReqRegister.bytes_0x769_reqbody = QByteArray::fromStdString(oidb0x769.SerializeAsString());
    svcReqRegister.bSetMute = 0;

    RequestPacket reqpack;
    reqpack.sServantName = "PushService";
    reqpack.sFuncName = "SvcReqRegister";
    reqpack.iRequestId = 0;
    reqpack.sBuffer = *svcReqRegister.serialize().getData();

    DataWriter ssoPacket(nullptr);
    writeSsoPacket(&ssoPacket,"StatSvc.register",nextSequenceId(),*reqpack.serialize().getData(),loginSig.tgt);
    DataWriter ret(nullptr);
    buildOutgoingPacket(&ret,loginSig.uin,1,*ssoPacket.getData(),loginSig.d2Key,loginSig.d2);
    return *ret.getData();
}

QByteArray PacketFactory::ConfigPushSvcPushResp(const LoginSigInfo &loginSig, const int &ssoSequenceId)
{
    //TODO USE JCE WRITER SERILIZER IT
    DataWriter body(nullptr);
    QByteArray data;
    data += QByteArray::fromHex("10 03 2C 3C 4C 56 23 51 51 53 65 72 76 69 63 65 2E 43 6F 6E 66 69 67 50 75 73 68 53 76 63 2E 4D 61 69 6E 53 65 72 76 61 6E 74 66 08 50 75 73 68 52 65 73 70 7D 00 00 1E 08 00 01 06 08 50 75 73 68 52 65 73 70 1D 00 00 0D 0A 10");
    data += QByteArray::fromHex("02");//body type (from req message)
    data += QByteArray::fromHex("23 00 00 00 00");
    data += Util::getBytes(ssoSequenceId);
    data += QByteArray::fromHex("0B 8C 98 0C A8 0C");
    body.writeRaw(data);

    //ConfigPushSvc_PushResp
    DataWriter ret(nullptr);
    buildResponseUniPacket(&ret,"ConfigPushSvc.PushResp",loginSig.d2Key,ssoSequenceId,loginSig.outgoingPacketSessionId,loginSig.uin,*body.getData(),1);
    return *ret.getData();
}

QByteArray PacketFactory::HeartbeatAlive(const LoginSigInfo &loginSig)
{
    DataWriter body(nullptr);
    DataWriter ssoPacket(nullptr);
    writeSsoPacket(&ssoPacket,"Heartbeat.Alive",nextSequenceId(),*body.getData());
    DataWriter ret(nullptr);
    buildOutgoingPacket(&ret,loginSig.uin,0,*ssoPacket.getData(),QByteArray());
    return *ret.getData();
}

QByteArray PacketFactory::GetTroopListReqV2Simplify(const LoginSigInfo &loginSig)
{
    TroopListReqV2Simplify body;
    body.uin = loginSig.uin;
    body.getMSFMsgFlag = 0;
    body.groupFlagExt = 1;
    body.shVersion = 7;
    body.dwCompanyId = 0;
    body.versionNum = 1;
    body.getLongGroupName = 1;
    RequestPacket reqpack;
    reqpack.sFuncName = "GetTroopListReqV2Simplify";
    reqpack.sServantName = "mqq.IMService.FriendListServiceServantObj";
    reqpack.iVersion = 3;
    reqpack.cPacketType = 0x00;
    reqpack.iMessageType = 0x00000;
    reqpack.iRequestId = nextRequestPacketRequestId();
    reqpack.sBuffer = *body.serialize().getData();

    DataWriter ret(nullptr);
    buildResponseUniPacket(&ret,"friendlist.GetTroopListReqV2",loginSig.d2Key,nextSequenceId(),loginSig.outgoingPacketSessionId,loginSig.uin,*reqpack.serialize().getData(),1);
    return *ret.getData();
}

QByteArray PacketFactory::SendMessageToGroup(const LoginSigInfo &loginSig,qint64 groupCode,const QString & content)
{
    qqprotobuf::MsgSvc::PbSendMsgReq pbSendMsgReq;
    pbSendMsgReq.mutable_routinghead()->mutable_grp()->set_groupcode(groupCode);
    pbSendMsgReq.mutable_contenthead()->set_pkgnum(1);
    pbSendMsgReq.mutable_msgbody()->mutable_richtext()->mutable_elems()->Add()->mutable_text()->set_str(content.toStdString());
    pbSendMsgReq.mutable_msgbody()->mutable_richtext()->mutable_elems()->Add()->mutable_generalflags()->set_pbreserve(Util::BytesStringToStd("78 00 F8 01 00 C8 02 00"));
    pbSendMsgReq.set_multisendseq(nextMessageSequenceId());
    pbSendMsgReq.set_msgrand(Util::getRandomNumber());
    pbSendMsgReq.set_synccookie("");
    pbSendMsgReq.set_msgvia(1);

    const QByteArray & data = QByteArray::fromStdString(pbSendMsgReq.SerializeAsString());

    DataWriter ret(nullptr);
    buildResponseUniPacket(&ret,"MessageSvc.PbSendMsg",loginSig.d2Key,nextSequenceId(),loginSig.outgoingPacketSessionId,loginSig.uin,data);
    return *ret.getData();
}

QByteArray PacketFactory::SendMessageToFriend(const LoginSigInfo &loginSig, qint64 targetUin, const QString &content)
{
    qqprotobuf::MsgSvc::PbSendMsgReq pbSendMsgReq;
    pbSendMsgReq.mutable_routinghead()->mutable_c2c()->set_touin(targetUin);
    pbSendMsgReq.mutable_contenthead()->set_pkgnum(1);
    pbSendMsgReq.mutable_msgbody()->mutable_richtext()->mutable_elems()->Add()->mutable_text()->set_str(content.toStdString());
    pbSendMsgReq.mutable_msgbody()->mutable_richtext()->mutable_elems()->Add()->mutable_generalflags()->set_pbreserve(Util::BytesStringToStd("78 00 F8 01 00 C8 02 00"));
    pbSendMsgReq.set_multisendseq(nextMessageSequenceId());
    pbSendMsgReq.set_msgrand(Util::getRandomNumber());
    qqprotobuf::SyncCookie syncCookie;
    syncCookie.set_time(Util::getUTCTime());
    syncCookie.set_unknown1(Util::getRandomNumber());
    syncCookie.set_unknown2(Util::getRandomNumber());
    syncCookie.set_const1(Util::getRandomNumber());
    syncCookie.set_const2(Util::getRandomNumber());
    syncCookie.set_unknown3(0x1d);
    syncCookie.set_unknown4(0);
    pbSendMsgReq.set_synccookie(syncCookie.SerializeAsString());
    //pbSendMsgReq.set_msgvia(1);

    const QByteArray & data = QByteArray::fromStdString(pbSendMsgReq.SerializeAsString());

    DataWriter ret(nullptr);
    buildResponseUniPacket(&ret,"MessageSvc.PbSendMsg",loginSig.d2Key,nextSequenceId(),loginSig.outgoingPacketSessionId,loginSig.uin,data);
    return *ret.getData();
}

QByteArray PacketFactory::GetMessage(const LoginSigInfo &loginSig,qqprotobuf::MsgSvc::SyncFlag syncFlag)
{
    qqprotobuf::MsgSvc::PbGetMsgReq pbGetMsgReq;
    pbGetMsgReq.set_msgreqtype(1);
    pbGetMsgReq.set_contextflag(1);
    pbGetMsgReq.set_rambleflag(0);
    pbGetMsgReq.set_latestramblenumber(20);
    pbGetMsgReq.set_otherramblenumber(3);
    pbGetMsgReq.set_onlinesyncflag(1);
    pbGetMsgReq.set_whispersessionid(0);
    pbGetMsgReq.set_syncflag(syncFlag);
    if(loginSig.c2cMessageSyncData.syncCookie.size() == 0){
        qqprotobuf::SyncCookie syncCookie;
        syncCookie.set_time(Util::getUTCTime());
        syncCookie.set_unknown1(Util::getRandomNumber());
        syncCookie.set_unknown2(Util::getRandomNumber());
        syncCookie.set_const1(Util::getRandomNumber());
        syncCookie.set_const2(Util::getRandomNumber());
        syncCookie.set_unknown3(0x1d);
        syncCookie.set_unknown4(0);
        pbGetMsgReq.set_synccookie(syncCookie.SerializeAsString());
    }else{
        pbGetMsgReq.set_synccookie(loginSig.c2cMessageSyncData.syncCookie.toStdString());
    }


    const QByteArray & data = QByteArray::fromStdString(pbGetMsgReq.SerializeAsString());

    DataWriter ret(nullptr);
    buildResponseUniPacket(&ret,"MessageSvc.PbGetMsg",loginSig.d2Key,nextSequenceId(),loginSig.outgoingPacketSessionId,loginSig.uin,data);
    return *ret.getData();
}

QByteArray PacketFactory::GetFriendList(const LoginSigInfo &loginSig)
{
    GetFriendListReq body;
    body.reqtype = 3;
    body.ifReflush = 1;
    body.uin = loginSig.uin;
    body.startIndex = 0;
    body.getfriendCount = 100;
    body.groupid = 0;
    body.ifGetGroupInfo = 0;
    body.groupstartIndex = 0;
    body.getgroupCount = 100;
    body.ifGetMSFGroup = 0;
    body.ifShowTermType = 1;
    body.version = 27;
    body.eAppType = 0;
    body.ifGetBothFlag = 0;
    body.ifGetDOVId = 0;
    body.vec0xd6bReq = QByteArray();
    qqprotobuf::Vec0xd50::ReqBody reqbody;
    reqbody.set_appid(10002);
    reqbody.set_reqksingswitch(1);
    reqbody.set_reqmusicswitch(1);
    reqbody.set_reqmutualmarklbsshare(1);
    reqbody.set_reqmutualmarkalienation(1);
    body.vec0xd50Req = QByteArray::fromStdString(reqbody.SerializeAsString());
    body.vecSnsTypelist = {13580L, 13581L, 13582L};

    RequestPacket reqpack;
    reqpack.sFuncName = "GetFriendListReq";
    reqpack.sServantName = "mqq.IMService.FriendListServiceServantObj";
    reqpack.iVersion = 3;
    reqpack.cPacketType = 0x003;
    reqpack.iMessageType = 0x00000;
    reqpack.iRequestId = 1921334514;
    reqpack.sBuffer = *body.serialize().getData();

    DataWriter ret(nullptr);
    buildResponseUniPacket(&ret,"friendlist.getFriendGroupList",loginSig.d2Key,nextSequenceId(),loginSig.outgoingPacketSessionId,loginSig.uin,*reqpack.setSBufferName("FL").serialize().getData(),1);
    return *ret.getData();
}

QByteArray PacketFactory::TestFavorite(const LoginSigInfo &loginSig)
{

}

uint PacketFactory::nextSequenceId()
{
    return sequenceId+=2;
}

uint PacketFactory::nextMessageSequenceId()
{
    return messageSequenceId+=2;
}

uint PacketFactory::nextRequestPacketRequestId()
{
    return requestPacketRequestId+=2;
}

uint PacketFactory::nextHighwayDataTransSequenceIdForGroup()
{
    return highwayDataTransSequenceIdForGroup+=2;
}

uint PacketFactory::nextHighwayDataTransSequenceIdForFriend()
{
    return highwayDataTransSequenceIdForFriend+=2;
}

void PacketFactory::writeOicqRequestPacket(DataWriter *baseData, uint uin, int cmd,const QByteArray & body, char encryptID)
{
    DataWriter encryptBody(nullptr);
    encryptMakeBody(&encryptBody,body);
    baseData->writeByte(0x02);//head
    baseData->writeShort(27+2+encryptBody.getSize());// orthodox algorithm
    baseData->writeShort(QQDeviceConfig::getInstance().protocolVersion);
    baseData->writeShort(cmd);
    baseData->writeShort(1);// const??
    baseData->writeInt(uin);
    baseData->writeByte(3);// originally const
    baseData->writeByte(encryptID);
    baseData->writeByte(0);// const8_always_0
    baseData->writeInt(2); // originally const
    baseData->writeInt(QQDeviceConfig::getInstance().appClientVersion);
    baseData->writeInt(0); // constp_always_0
    baseData->writeRaw(encryptBody);
    baseData->writeByte(0x03);//tail
}

void PacketFactory::encryptMakeBody(DataWriter *baseData, const QByteArray & body)
{
    baseData->writeByte(1);
    baseData->writeByte(1);
    baseData->writeRaw(QQDeviceConfig::getInstance().randomKey);
    baseData->writeShort(258);
    baseData->writeBytes(ECDHCrypt::getPublicKey(),DataWriter::LengthType::SHORT);
    TEA::setKey(ECDHCrypt::getShareKey());
    baseData->writeRaw(TEA::encrypt(body));
}

void PacketFactory::writeSsoPacket(DataWriter *baseData, const QString &cmdName, uint sequenceId, const QByteArray & body, const QByteArray &extraData)
{
    DataWriter ssoHeader(nullptr);
    ssoHeader.writeInt(sequenceId);
    ssoHeader.writeInt(QQDeviceConfig::getInstance().subAppId);
    ssoHeader.writeInt(QQDeviceConfig::getInstance().subAppId);
    ssoHeader.writeRaw(QByteArray::fromHex("01 00 00 00 00 00 00 00 00 00 01 00"));
    ssoHeader.writeBytes(extraData,DataWriter::LengthType::INT,-1,4);
    ssoHeader.writeBytes(cmdName.toUtf8(),DataWriter::LengthType::INT,-1,4);
    ssoHeader.writeBytes(QByteArray::fromHex("02 B0 5B 8B"),DataWriter::LengthType::INT,-1,4);
    ssoHeader.writeBytes(QQDeviceConfig::getInstance().imei,DataWriter::LengthType::INT,-1,4);
    ssoHeader.writeInt(4);
    ssoHeader.writeBytes(QQDeviceConfig::getInstance().ksid,DataWriter::LengthType::SHORT,-1,2);
    ssoHeader.writeInt(4);

    baseData->writeBytes(*ssoHeader.getData(),DataWriter::LengthType::INT,-1,4);
    baseData->writeBytes(body,DataWriter::LengthType::INT,-1,4);
}

void PacketFactory::buildOutgoingPacket(DataWriter *baseData, uint uin, char bodyType, const QByteArray & body, const QByteArray &key, const QByteArray &extraData)
{
    DataWriter packet(nullptr);
    packet.writeInt(0x0A);
    packet.writeByte(bodyType);
    packet.writeBytes(extraData,DataWriter::LengthType::INT,-1,4);
    packet.writeByte(0);
    packet.writeBytes(QString::number(uin).toUtf8(),DataWriter::LengthType::INT,-1,4);
    if(key.size() >= 16){
        TEA::setKey(key);
        packet.writeRaw(TEA::encrypt(body));
    }else{
        packet.writeRaw(body);//no encrypt
    }
    baseData->writeBytes(*packet.getData(),DataWriter::LengthType::INT,-1,4);
}

void PacketFactory::buildResponseUniPacket(DataWriter *baseData, const QString &cmdName, const QByteArray &key, int sequenceId, const QByteArray &outgoingPacketSessionId, uint uin,const QByteArray & body, qint8 bodyType, const QByteArray &extraData)
{
    DataWriter UniPacket(nullptr);
    writeUniPacket(&UniPacket,cmdName,outgoingPacketSessionId,body,extraData);

    DataWriter repHeaer(nullptr);
    repHeaer.writeInt(0x0B);
    repHeaer.writeByte(bodyType);
    repHeaer.writeInt(sequenceId);
    repHeaer.writeByte(0);
    repHeaer.writeBytes(QString::number(uin).toUtf8(),DataWriter::LengthType::INT,-1,4);
    TEA::setKey(key);
    repHeaer.writeRaw(TEA::encrypt(*UniPacket.getData()));
    baseData->writeBytes(*repHeaer.getData(),DataWriter::LengthType::INT,-1,4);
}

void PacketFactory::writeUniPacket(DataWriter *baseData, const QString &cmdName, const QByteArray &outgoingPacketSessionId, const QByteArray & body, const QByteArray &extraData)
{
    DataWriter header(nullptr);
    header.writeBytes(cmdName.toUtf8(),DataWriter::LengthType::INT,-1,4);
    header.writeBytes(outgoingPacketSessionId,DataWriter::LengthType::INT,4);
    header.writeBytes(extraData,DataWriter::LengthType::INT,-1,4);

    baseData->writeBytes(*header.getData(),DataWriter::LengthType::INT,-1,4);
    baseData->writeBytes(body,DataWriter::LengthType::INT,-1,4);
}
